package sf.database.mapper.handle;

import sf.common.wrapper.Page;
import sf.database.dao.DBClient;
import sf.database.mapper.annotation.ExecuteSQL;
import sf.database.mapper.annotation.SelectKey;
import sf.database.support.DMLType;
import sf.tools.ArrayUtils;
import sf.tools.JavaTypeUtils;
import sf.tools.StringUtils;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * dao2 参数
 * @author xiandafu
 */
public class MethodHandleSQL {

    protected static MethodHandleSQL ms = new MethodHandleSQL();

    public static MethodHandleSQL getInstance() {
        return ms;
    }

    private MethodHandleSQL() {

    }

    public Object call(DBClient dbClient, Class entityClass, Method m, Object[] params) {

        Object ret = null;
        ExecuteSQL es = m.getAnnotation(ExecuteSQL.class);

        String sql = StringUtils.join(es.value());

        DMLType type = HandleHelp.getTypeBySql(sql);
        switch (type) {
            case SELECT:
                ret = parseSelectList(m, dbClient, sql, params);
                break;
            case INSERT:
                ret = parseInsert(m, dbClient, sql, params, es.batch());
                break;
            default:
                ret = parseUpdate(m, dbClient, sql, params);
                break;
        }
        return ret;
    }


    protected Object parseInsert(Method method, DBClient dbClient, String sql, Object[] params, boolean batch) {
        SelectKey returnKey = method.getAnnotation(SelectKey.class);
        List<String> pkeys = Collections.emptyList();
        if (returnKey != null && StringUtils.isNotBlank(returnKey.keyColumn())) {
            pkeys = Collections.singletonList(returnKey.keyColumn());
        }
        List<Map<String, Object>> rMap = new LinkedList<>();
        List<Object[]> pa = new ArrayList<>();
        if (!batch) {
            pa.add(params);
        } else {
            if (params != null && params.length > 0) {
                pa.addAll((Collection<? extends Object[]>) params[0]);
            }
        }
        dbClient.executeBatch(sql, pa, false, 100, pkeys, rMap);
        if (!rMap.isEmpty() && returnKey != null) {
            Object value = rMap.get(0).values().iterator().next();
            if (returnKey.resultType() == Integer.class || returnKey.resultType() == int.class) {
                return ((Number) value).intValue();
            } else if (returnKey.resultType() == Long.class || returnKey.resultType() == long.class) {
                return ((Number) value).longValue();
            } else if (returnKey.resultType() == Map.class) {
                return rMap.get(0);
            }
        }
        return null;
    }

    /**
     * 根据返回参数int 或者int[] 判断是否是批处理。如果都没有，根据第一参数判断
     * @param method
     * @param dbClient
     * @param sql
     * @param params
     * @return
     */
    protected Object parseUpdate(Method method, DBClient dbClient, String sql, Object[] params) {
        Class[] paras = method.getParameterTypes();
        Class ret = method.getReturnType();
        boolean batch = false;
        if (JavaTypeUtils.isInt(ret)) {
            batch = false;
        } else if (ret.isArray()) {
            //如果更新语句返回了int[],
            Class type = ret.getComponentType();
            if (JavaTypeUtils.isInt(type)) {
                batch = true;
            }
        }
        //通过输入参数判断
        if (paras.length == 1) {
            Class first = paras[0];
            if (List.class.isAssignableFrom(first) && this.isUpdateBatchByFirstList(method)) {
                batch = true;
            } else if (first.isArray()) {
                Class ct = first.getComponentType();
                if (this.isPojo(ct)) {
                    batch = true;
                }
            }
        }
        if (batch) {
            return dbClient.executeBatch(sql, (List<Object[]>) params[0]);
        } else {
            return dbClient.execute(sql, params);
        }
    }

    private boolean isUpdateBatchByFirstList(Method method) {

        Type firstType = method.getGenericParameterTypes()[0];
        Class type = HandleHelp.getType(firstType);
        if (type == null) {
            //不知道List里面是什么，认为是batchUpdate,兼容以前情况
            return true;
        }
        return isPojo(type);

    }

    private boolean isPojo(Class type) {
        if (type.isPrimitive()) {
            return false;
        }
        if (Map.class.isAssignableFrom(type)) {
            return true;
        }

        String pkg = getPackageName(type);
        return !pkg.startsWith("java.") && !pkg.startsWith("javax.");
    }

    public static String getPackageName(Class<?> clazz) {
        return StringUtils.substringBefore(clazz.getName(), ".");
    }

    protected Object parseSelectList(Method method, DBClient dbClient, String sql, Object[] paras) {
        if (paras == null) {
            paras = ArrayUtils.EMPTY_OBJECT_ARRAY;
        }
        Class<?> returnType = method.getReturnType();
        Type retType = method.getGenericReturnType();
        Class[] paraClass = method.getParameterTypes();
        if (returnType == Page.class) {
            Class resultType = HandleHelp.getPageType(retType, Map.class);
            RowLimit rl = new RowLimit(0, 10);//默认
            List<Object> list = new ArrayList<>();
            for (int i = 0; i < paraClass.length; i++) {
                Class<?> clz = paraClass[i];
                if (clz == RowLimit.class) {
                    rl = (RowLimit) paras[i];
                } else {
                    list.add(clz);
                }
            }
            return dbClient.selectPage(rl.getOffset(), rl.getLimit(), resultType, sql, list.toArray());
            //else否则就默认为mapper类型
        }
        if (Map.class.isAssignableFrom(returnType)) {
            //如果定义返回结果为Map，无论是否泛型，都认为返回一个Map
            return dbClient.selectOne(Map.class, sql, paras);
        } else if (List.class.isAssignableFrom(returnType)) {
            Class type = HandleHelp.getType(retType);
            if (type == null) {
                type = Map.class;
            }
            return dbClient.selectList(type, sql, paras);
        }

        //更改类型为Single
        return dbClient.selectOne(returnType, sql, paras);

    }
}
